﻿#region Usings

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Web;
using System.Web.Caching;
using Ninject;
using Rail.Net.Common;
using Rail.Net.Common.Extensions;
using Rail.Net.Core.Interfaces.Data;
using Rail.Net.Core.Interfaces.Services;
using Rail.Net.Core.Model.Nonpersistent;
using Rail.Net.Core.WebServices;

#endregion

namespace Rail.Net.Services
{
    public class TransportPlanService : ITransportPlanService
    {
        private const string Url = "http://fahrplan.oebb.at/bin/query.exe/dn";

        //private const string Url = "http://195.69.194.143/bin/fahrplan/query.exe/dn";

        private Cache _cache;

        public Cache Cache
        {
            get
            {
                if (_cache != null)
                {
                    return _cache;
                }
                _cache = HttpRuntime.Cache;
                return _cache;
            }
        }

        [Inject]
        public IDalContext DalContext { get; set; }

        #region ITransportPlanService Members

        public TransportPlanModel GetTransportPlan(SearchModel searchModel, bool forwardDirection)
        {
            WebRequest req = null;
            WebResponse rsp = null;

            var transportPlanCacheKey = forwardDirection
                                            ? searchModel.FromTransportPlanCacheKey
                                            : searchModel.ToTransportPlanCacheKey;
            var result = Cache[transportPlanCacheKey] as TransportPlanModel;

            if (result == null)
            {
                var xml = searchModel.PrepareTransportPlanXml(forwardDirection);
                var fullDate = forwardDirection ? searchModel.FromDate : searchModel.ToDate.Value;
                var date = new DateTime(fullDate.Year, fullDate.Month, fullDate.Day);

                var borders =
                    DalContext.StationRepository.AllDefaultsIncluded().Where(s => s.IsBorder).Select(
                        s => new BorderModel {Eva = s.Eva, EvaA = s.BorderEva, Correction = s.BorderCorrection}).ToList();

                var connectionId = 0;
                var connections = new List<ConnectionModel>();
                try
                {
                    req = WebRequest.Create(Url);
                    //var proxy = new WebProxy("fpt.oebb.at", 80);
                    //proxy.Credentials = new NetworkCredential("ext-jovanovg", "Gj12345!", "PV");
                    req.Proxy = new WebProxy(); // Enable if using proxy
                    req.Method = "POST"; // Post method
                    req.ContentType = "text/xml"; // content type

                    // Wrap the request stream with a text-based writer
                    var writer = new StreamWriter(req.GetRequestStream());
                    // Write the XML text into the stream
                    var xmlContent = xml.ToString(false);
                    writer.Write(xmlContent);
                    writer.Close();

                    // Send the data to the webserver
                    rsp = req.GetResponse();

                    var reader = new StreamReader(rsp.GetResponseStream(), Encoding.GetEncoding("iso-8859-1"));
                    var xmlData = ServiceHelper.ConvertToUtf8(reader.ReadToEnd());


                    var responseData = ResC.Deserialize(xmlData);

                    if (responseData.Items.Count > 0)
                    {
                        var contengentTrainLabels = DalContext.ContengentTrainLabelRepository.All();
                        var contengentTrainNumbers = DalContext.ContengentTrainNumberRepository.All();

                        var globalTrainLabels = DalContext.GlobalTrainLabelRepository.All();
                        var globalTrainNumbers = DalContext.GlobalTrainNumberRepository.All();

                        var reservableTrainLabels = DalContext.ReservableTrainLabelRepository.All();
                        var reservableTrainNumbers = DalContext.ReservableTrainNumberRepository.All();

                        var virtualRuTrainNumbers = DalContext.VirtualRuTrainNumberRepository.AllDefaultsIncluded();


                        var hafasConnections = ((ConRes) responseData.Items[0]).ConnectionList.Connection;

                        
                        foreach (var connection in hafasConnections.AsParallel()
                                                                   .WithDegreeOfParallelism(Constants.MaxNumberOfThreads))
                        {
                            var overview = connection.Overview;
                            var transfer = overview.Transfers;
                            var duration = overview.Duration.Time.Text[0].CreateTimeSpan();
                            var departure = overview.Departure.BasicStop.Dep.Time.Text[0].CreateDateTime(date);
                            var arrival = overview.Arrival.BasicStop.Arr.Time.Text[0].CreateDateTime(date);

                            var connectionModel = new ConnectionModel(borders, contengentTrainLabels,
                                                                      contengentTrainNumbers, globalTrainLabels,
                                                                      globalTrainNumbers, reservableTrainLabels,
                                                                      reservableTrainNumbers,
                                                                      virtualRuTrainNumbers)
                                                      {
                                                          Id = connectionId++,
                                                          Transfers = transfer,
                                                          Duration = duration,
                                                          DepartureTime = departure,
                                                          ArrivalTime = arrival
                                                      };

                            var trainId = 0;
                            var stationId = 0;
                            foreach (var train in connection.ConSectionList)
                            {
                                var trainName = "";
                                var trainNumber = "";
                                var trainLabel = "";
                                string journeyType;
                                if (train.Item is Journey)
                                {
                                    journeyType = "Train";
                                }
                                else if (train.Item is Walk)
                                {
                                    journeyType = "Walk";
                                }
                                else
                                {
                                    journeyType = "Transfer";
                                }
                                if (((IConnection) train.Item).JourneyAttributeList.Count > 0)
                                {
                                    trainName =
                                        ((IConnection) train.Item).JourneyAttributeList[0].Attribute.
                                            AttributeVariant[0].
                                            Text[0].Text1[0];
                                }

                                var trainComponents = trainName.Split(new[] {' '},
                                                                      StringSplitOptions.RemoveEmptyEntries);
                                if (trainName != "")
                                {
                                    trainLabel = trainComponents[0];

                                    if (trainComponents.Length > 1)
                                        trainNumber = trainComponents[1];
                                }


                                var departureEva =
                                    train.Departure.BasicStop.Item.externalStationNr.TrimStart(new[] {'0'});
                                var departureName = train.Departure.BasicStop.Item.name;
                                var departureX = train.Departure.BasicStop.Item.x;
                                var departureY = train.Departure.BasicStop.Item.y;
                                var departureTime = train.Departure.BasicStop.Dep.Time.Text[0].CreateDateTime(date);

                                var departureStation = new StationModel
                                                           {
                                                               Eva = departureEva,
                                                               Name = departureName,
                                                               X = departureX,
                                                               Y = departureY,
                                                               DepartureTime = departureTime
                                                           };

                                var arrivalEva =
                                    train.Arrival.BasicStop.Item.externalStationNr.TrimStart(new[] {'0'});
                                var arrivalName = train.Arrival.BasicStop.Item.name;
                                var arrivalX = train.Arrival.BasicStop.Item.x;
                                var arrivalY = train.Arrival.BasicStop.Item.y;
                                var arrivalTime = train.Arrival.BasicStop.Arr.Time.Text[0].CreateDateTime(date);


                                var arrivalStation = new StationModel
                                                         {
                                                             Eva = arrivalEva,
                                                             Name = arrivalName,
                                                             X = arrivalX,
                                                             Y = arrivalY,
                                                             ArrivalTime = arrivalTime
                                                         };

                                var trainModel = new TrainModel
                                                     {
                                                         Id = trainId++,
                                                         Label = trainLabel,
                                                         JourneyType = journeyType,
                                                         Number = trainNumber,
                                                         Departure = departureStation,
                                                         Arrival = arrivalStation
                                                     };

                                if (train.Item is Journey)
                                {
                                    foreach (var stepStation in
                                        ((Journey) train.Item).PassList.Select(
                                            (step, index) =>
                                            new
                                                {
                                                    index,
                                                    step,
                                                    stepEva = step.Item.externalStationNr.TrimStart(new[] {'0'})
                                                })
                                            .Select(@t => new {@t, stepName = @t.step.Item.name})
                                            .Select(@t => new {@t, stepX = @t.@t.step.Item.x})
                                            .Select(@t => new {@t, stepY = @t.@t.@t.step.Item.y})
                                            .Select(@t => new StationModel
                                                              {
                                                                  Id = stationId + @t.@t.@t.@t.index,
                                                                  Eva = @t.@t.@t.@t.stepEva,
                                                                  Name = @t.@t.@t.stepName,
                                                                  X = @t.@t.stepX,
                                                                  Y = @t.stepY
                                                              }))
                                    {
                                        trainModel.Steps.Add(stepStation);
                                    }
                                    var firstStation = trainModel.Steps.First();
                                    var lastStation = trainModel.Steps.Last();

                                    departureStation.Id = firstStation.Id;
                                    arrivalStation.Id = lastStation.Id;

                                    stationId = lastStation.Id;
                                }

                                connectionModel.Trains.Add(trainModel);
                            }
                            if (connectionModel.DepartureTime >= fullDate)
                            {
                                connections.Add(connectionModel);
                            }
                        }
                    }
                }
                catch (WebException webEx)
                {
                    throw webEx;
                }
                catch (Exception ex)
                {
                    throw ex;
                }
                finally
                {
                    if (req != null) req.GetRequestStream().Close();
                    if (rsp != null) rsp.GetResponseStream().Close();
                }

                // correct the connection id
                connectionId = 0;
                foreach (var connectionModel in connections)
                {
                    connectionModel.Id = connectionId++;
                }

                result = new TransportPlanModel(connections);

                // store the transport plan in cache
                Cache.Insert(transportPlanCacheKey, result, null, Cache.NoAbsoluteExpiration, new TimeSpan(1, 0, 0, 0));
            }

            return result;
        }

        public void PrepareDataModels(TransportPlanModel transportPlanModel)
        {
            // load the all stations and set them as a corresponding data model
            var stations = DalContext.StationRepository.AllDefaultsIncluded();
            var allEvas = transportPlanModel.Connections.SelectMany(con => con.AllStations).Select(s => s.Eva).Distinct();

            foreach (var eva in allEvas.AsParallel())
            {
                transportPlanModel.Connections.SelectMany(con => con.Trains.SelectMany(t => t.Steps)).Where(
                    s => s.Eva == eva).ToList().
                    ForEach(s =>
                                {
                                    var station = stations.SingleOrDefault(st => st.Eva == s.Eva);
                                    if (station != null)
                                    {
                                        station.AlternativeStation = station.BorderEva != null
                                                                         ? stations.SingleOrDefault(
                                                                             st => st.Eva == station.BorderEva)
                                                                         : null;
                                        s.DataModel = station;
                                    }
                                });
            }

            // calculating the Distances for all Domestic Sequence station pairs
            var tispTispList = new SortedSet<string>();
            var pairList = new Dictionary<DomesticSequenceModel, List<string>>();
            foreach (var connection in transportPlanModel.Connections.AsParallel())
            {
                foreach (var sequence in connection.DomesticSequences.Union(connection.GlobalSequences.OfType<DomesticSequenceModel>()))
                {
                    var pairs = sequence.Sequence.Select(s => s.DataModelForDistance).ConsecutivePairs();
                    if (pairs.All(p => p.Item1 != null && p.Item2 != null))
                    {
                        var tispTisp = pairs.Select(pair => pair.Item1.Tisp + pair.Item2.Tisp).Union(
                                       pairs.Select(pair => pair.Item2.Tisp + pair.Item1.Tisp)).ToList();
                        pairList.Add(sequence, tispTisp);
                        tispTisp.ForEach(s => tispTispList.Add(s));
                    }
                    else
                    {
                        var a = 9;
                    }
                }
            }

            // set the Distances to the corresponding sequences
            var allDistances = DalContext.DistanceRepository.GetDistancesForEvas(tispTispList).ToList();
            foreach (var pair in pairList)
            {
                var distances = allDistances.Where(d => pair.Value.Contains(d.TispTisp)).ToList();
                pair.Key.Distances = distances;
            }
            
            // calculate the Relations for the International Sequences
            var relations = DalContext.RelationRepository.AllDefaultsIncluded();
            foreach (var sequence in transportPlanModel.Connections.SelectMany(c => c.InternationalSequences)
                                     .Union(transportPlanModel.Connections.SelectMany(c => c.GlobalInternationalSequences))
                                     .AsParallel()
                                     .WithDegreeOfParallelism(8)
                                     .WithExecutionMode(ParallelExecutionMode.ForceParallelism))
            {
                //var allStations = sequence.Connection.AllStations.Select(s => s.Eva);

                var allStations = sequence.Sequence.Select(s => s.Eva);

                var borderRelations =
                    relations.Where(
                        r =>
                        r.Descriptions.Where(d => d.StationType == Constants.RelationDescriptionBorder && d.Included).Select
                            (d => d.Station.Eva).Any(e => e == sequence.FromStation.Eva || e == sequence.ToStation.Eva)
                        &&
                        !r.Descriptions.Where(d => d.StationType == Constants.RelationDescriptionBorder && !d.Included).
                             Select(d => d.Station.Eva).Any(
                                 e => e == sequence.FromStation.Eva || e == sequence.ToStation.Eva));

                var destinationRelations =
                    relations.Where(
                        r =>
                        r.Descriptions.Where(d => d.StationType == Constants.RelationDescriptionDestination && d.Included).
                            Select(d => d.Station.Eva).Any(e => e == sequence.FromStation.Eva || e == sequence.ToStation.Eva)
                        &&
                        !r.Descriptions.Where(d => d.StationType == Constants.RelationDescriptionDestination && !d.Included)
                             .Select(d => d.Station.Eva).Any(
                                 e => e == sequence.FromStation.Eva || e == sequence.ToStation.Eva));

                var viaRelations = borderRelations.Intersect(destinationRelations)
                    .Where(r => r.Descriptions.Where(d => d.StationType == Constants.RelationDescriptionVia && d.Included)
                                    .Select(d => d.Station.Eva).All(e => allStations.Contains(e))
                                &&
                                !r.Descriptions.Where(d => d.StationType == Constants.RelationDescriptionVia && !d.Included)
                                     .Select(d => d.Station.Eva).Any(e => allStations.Contains(e))
                    ).ToList();
                sequence.Relation = viaRelations.FirstOrDefault();
            }
        }

        #endregion
    }
}